package com.hiss.basic.admin.service;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.hiss.basic.admin.model.request.AuthenticationRequest;
import com.hiss.basic.admin.model.response.AuthenticationResponse;
import com.hiss.basic.client.system.ISysRoleMenuService;
import com.hiss.basic.client.system.ISysUserRoleService;
import com.hiss.basic.client.system.ISysUserService;
import com.hiss.basic.model.system.domain.SysMenuDomain;
import com.hiss.basic.model.system.domain.SysRoleDomain;
import com.hiss.basic.model.system.domain.SysRoleMenuDomain;
import com.hiss.basic.model.system.domain.SysUserDomain;
import com.hiss.basic.starter.core.constant.SystemConstant;
import com.hiss.basic.starter.core.enums.EnableStatusEnum;
import com.hiss.basic.starter.core.utils.ServletUtils;
import com.hiss.basic.starter.core.helper.ValidationHelper;
import com.hiss.basic.starter.log.event.LoginLogEvent;
import com.hiss.basic.starter.security.exception.DisabledException;
import com.hiss.basic.starter.security.exception.UsernameNotFoundException;
import com.hiss.basic.starter.security.helper.SecurityHelper;
import com.hiss.basic.starter.security.model.AuthenticationModel;
import com.hiss.basic.system.helper.SysUserPasswordHelper;
import com.mybatisflex.core.query.QueryChain;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class AuthenticationService {

    @Resource
    private ISysUserService sysUserService;

    @Resource
    private ISysUserRoleService sysUserRoleService;

    @Resource
    private ISysRoleMenuService sysRoleMenuService;

    public AuthenticationResponse login(AuthenticationRequest request) {
        ValidationHelper.validate(request);

        SysUserDomain userInfo = sysUserService.findByUsername(request.getUsername());
        if (ObjectUtil.isNull(userInfo)) {
            logFailedLogin(request.getUsername());
            throw new UsernameNotFoundException("账户不存在");
        }

        if (!SysUserPasswordHelper.verifyPassword(request.getPassword(), userInfo.getPassword())) {
            logFailedLogin(request.getUsername());
            throw new UsernameNotFoundException("账户或密码错误");
        }

        if (ObjectUtil.notEqual(EnableStatusEnum.ENABLE.getCode(), userInfo.getEnableStatus())) {
            logFailedLogin(request.getUsername());
            throw new DisabledException("账户已禁用");
        }

        AuthenticationModel authenticationModel = convert(userInfo);

        if (SecurityHelper.isSuperAdmin(userInfo.getId())) {
            setAdminAuthority(authenticationModel);
        } else {
            setUserAuthority(authenticationModel);
        }

        SecurityHelper.login(authenticationModel);
        return new AuthenticationResponse(StpUtil.getTokenValue());
    }

    public void logout() {
        StpUtil.logout();
    }

    /**
     * 获取认证信息
     *
     * @param userId 用户 ID
     * @return 认证模型
     */
    public AuthenticationModel getAuthentication(Long userId) {
        SysUserDomain userInfo = sysUserService.getById(userId);

        AuthenticationModel authenticationModel = convert(userInfo);

        if (SecurityHelper.isSuperAdmin(userInfo.getId())) {
            setAdminAuthority(authenticationModel);
        } else {
            setUserAuthority(authenticationModel);
        }

        return authenticationModel;
    }

    /**
     * 转换认证信息
     *
     * @param userInfo 用户信息
     * @return 认证模型
     */
    private AuthenticationModel convert(SysUserDomain userInfo) {
        AuthenticationModel authenticationModel = new AuthenticationModel();
        authenticationModel.setUserId(userInfo.getId());
        authenticationModel.setUsername(userInfo.getUsername());
        authenticationModel.setNickname(userInfo.getNickname());
        authenticationModel.setAvatar(userInfo.getAvatar());
        authenticationModel.setEmail(userInfo.getEmail());
        authenticationModel.setRoles(ListUtil.toList());
        authenticationModel.setCodes(ListUtil.toList());
        return authenticationModel;
    }

    /**
     * 设置管理员权限
     *
     * @param authenticationModel 认证模型
     */
    private void setAdminAuthority(AuthenticationModel authenticationModel) {
        authenticationModel.setRoles(ListUtil.toList(SystemConstant.ADMIN_ROLE_CODE));
        authenticationModel.setCodes(ListUtil.toList(SystemConstant.ADMIN_AUTH_CODE));
    }

    /**
     * 设置普通用户权限
     *
     * @param authenticationModel 认证模型
     */
    private void setUserAuthority(AuthenticationModel authenticationModel) {
        Collection<Long> userRoleIdList = sysUserRoleService.findRoleIdListByUserId(authenticationModel.getUserId());
        if (CollectionUtil.isEmpty(userRoleIdList)) {
            return;
        }
        List<SysRoleDomain> roleList = QueryChain.of(SysRoleDomain.class)
                .in(SysRoleDomain::getId, userRoleIdList)
                .eq(SysRoleDomain::getEnableStatus, EnableStatusEnum.ENABLE.getCode())
                .list();

        if (CollectionUtil.isNotEmpty(roleList)) {
            authenticationModel.setRoles(roleList.stream()
                    .map(SysRoleDomain::getCode)
                    .collect(Collectors.toSet()));

            List<SysRoleMenuDomain> roleMenuList = sysRoleMenuService.findList(roleList.stream().map(SysRoleDomain::getId).collect(Collectors.toSet()));
            if (CollectionUtil.isEmpty(roleMenuList)) {
                return;
            }

            Set<Long> menuIdList = roleMenuList.stream()
                    .map(SysRoleMenuDomain::getMenuId)
                    .collect(Collectors.toSet());

            List<SysMenuDomain> menuList = QueryChain.of(SysMenuDomain.class)
                    .in(SysMenuDomain::getId, menuIdList)
                    .eq(SysMenuDomain::getEnableStatus, EnableStatusEnum.ENABLE.getCode())
                    .list();

            if (CollectionUtil.isNotEmpty(menuList)) {
                authenticationModel.setCodes(menuList.stream()
                        .map(SysMenuDomain::getAuthority)
                        .filter(StrUtil::isNotBlank)
                        .collect(Collectors.toSet()));
            }
        }
    }

    private void logFailedLogin(String username) {
        LoginLogEvent event = new LoginLogEvent();
        event.setUsername(username);
        event.setStatus(1);
        event.setRequest(ServletUtils.getRequest());
        SpringUtil.getApplicationContext().publishEvent(event);
    }
}
